package haxeframe.loading;

import flash.events.ProgressEvent;
import haxe.Json;
import haxe.Timer;

import haxeframe.events.LoadEvent;
import haxeframe.Globals;
import haxeframe.loading.LoadObject;
import haxeframe.utils.Utils;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.geom.Matrix;
import flash.media.Sound;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.system.System;
import flash.text.Font;

class LoadManager
{
	public static var _queues:Map<String, Array<Array<Dynamic>>> = new Map<String, Array<Array<Dynamic>>>();
	public static var _queueQueue:Array<String> = new Array<String>();

	private static var _files:Map<String, LoadObject> = new Map<String, LoadObject>();
	private static var _filesByURL:Map<String, String> = new Map<String, String>();

	private static var dispatcher:EventDispatcher = new EventDispatcher();

	public static inline var FILETYPE_FONT:String = 'font';
	public static inline var FILETYPE_IMAGE:String = 'image';
	public static inline var FILETYPE_SOUND:String = 'sound';
	public static inline var FILETYPE_TEXT:String = 'text';

	static inline var EXTENSION_MP3 = "mp3";
	static inline var EXTENSION_OGG = "ogg";
	static inline var EXTENSION_WAV = "wav";
	static inline var EXTENSION_AAC = "aac";

	static inline var MEDIA_TYPE_MP3 = "audio/mpeg";
	static inline var MEDIA_TYPE_OGG = "audio/ogg; codecs=\"vorbis\"";
	static inline var MEDIA_TYPE_WAV = "audio/wav; codecs=\"1\"";
	static inline var MEDIA_TYPE_AAC = "audio/mp4; codecs=\"mp4a.40.2\"";

	private static var _extensions_sound:Array<String> = ['mp3','wav','aac','ogg','sound'];
	private static var _extensions_text:Array<String> = ['html','txt','xml','json'];
	private static var _extensions_image:Array<String> = ['png','jpg'];
	private static var _extensions_font:Array<String> = ['ttf','otf'];

	public static var queueIndex:Int = 0;

	private static var currentQueueIndex:Int = 0;

	public static var currentQueue:String;

	public static var online:Bool = true;

	public static var basePath:String = '';

	public static var scope:Dynamic;

	public static var respectFrameRate:Int = 30;

	private static var cachedBitmapData:Map<String, BitmapData> = new Map<String, BitmapData>();

	public static var paused:Bool;

	private static var loadTimer:Timer;
	private static var pauseTimer:Timer;
	private static var nextQueueTimer:Timer;

	private static var memoryLoaded:Int = 16000000;

	public static var progressHandler:Float->Void;
	public static var completeHandler:LoadObject->Void;

	public static var progressiveLoad:Bool = true;

	private static var listeners:Array<Array<Dynamic>> = [];

	public static function reset():Void
	{
		listeners = [];
		_queues = new Map<String, Array<Array<Dynamic>>>();
		_queueQueue = new Array<String>();
		_files = new Map<String, LoadObject>();
		dispatcher = new EventDispatcher();
		cachedBitmapData = new Map<String, BitmapData>();
		System.gc();
	}

	public static function addQueue(name:String, queue:Array<Array<String>>):Void
	{
		if(name == null) return;
		if(_queues.get(name) != null){
			trace('Overwriting queue "'+name+'".');
		}
		_queues.set(name, queue);
		_queueQueue.push(name);
	}

	public static function getQueue(name:String):Array<Array<Dynamic>>
	{
		var queue:Array<Array<Dynamic>> = _queues.get(name);
		if(queue == null){
			addQueue(name, []);
			queue = _queues.get(name);
		}
		return(queue);
	}

	public static function loadFilesFromXml(xml:Xml, queue:String=null):Void
	{
		loadXmlChildren(xml, queue);
	}

	private static function loadXmlChildren(xml:Xml, queue:String=null):Void
	{
		for(element in xml.elements())
		{
			var name:String = (element.exists('name')) ? element.get('name') : element.get('file');
			var url:String = element.get('file');
			var channel:String = element.get('channel');
			if(url != null)
			{
				addFile(name, url, queue, channel);
			}
			loadXmlChildren(element, queue);
		}
	}

	public static function loadFromQueue():Void
	{
		if(paused){
			if(loadTimer != null) loadTimer.stop();
			loadTimer = Timer.delay(loadFromQueue, Math.ceil(1000/respectFrameRate));
			return;
		}
		var queue:Array<Array<Dynamic>> = getQueue(currentQueue);

		if(queueIndex >= queue.length){
			nextQueueTimer = Timer.delay(function():Void{
				var le:LoadEvent = new LoadEvent(LoadEvent.QUEUE_COMPLETE);
				le.queue = currentQueue;
				dispatcher.dispatchEvent(le);
				if(progressiveLoad)
						loadNextQueue();
			}, Math.ceil(1000/respectFrameRate));
			return;
		}

		var file:Array<Dynamic> = queue[queueIndex];
		if(file != null){
			++queueIndex;
			var loader:Dynamic = loadFile(file[0], file[1], currentQueue, file[3], file[4], file[5], file[6]);
		}
	}

	private static function loadNextQueue():Void
	{
		++currentQueueIndex;
		queueIndex = 0;
		currentQueue = _queueQueue[currentQueueIndex];
		if(currentQueue != null){
			loadFromQueue();
		}
	}

	public static function startQueue(queue:String):Void
	{
		// if(currentQueue == queue){
		// 	return;
		// }
		if(nextQueueTimer != null){
			nextQueueTimer.stop();
			nextQueueTimer = null;
		}
		queueIndex = 0;
		for(i in 0..._queueQueue.length)
		{
			if(_queueQueue[i] == queue){
				if(loadTimer != null) loadTimer.stop();
				currentQueueIndex = i;
				currentQueue = queue;
				loadFromQueue();
				return;
			}
		}
	}

	public static function startQueues():Void
	{
		if(currentQueue == null)
			currentQueue = _queueQueue[currentQueueIndex];

		if(currentQueue != null){
			loadFromQueue();
		}
	}

	public static function getFile(fileName:String):Dynamic
	{
		var loadObject:LoadObject = _files.get(fileName);
		if(loadObject == null){
			return null;
		}

		var file:Dynamic = loadObject.file;
		if(file == null){
			return null;
		} 

		var tempUrl:String = loadObject.url;
		var rx:EReg = ~/[\.\-\/]/g;
		tempUrl = rx.replace(tempUrl.toLowerCase(),'_');
		var extension:String = tempUrl.split('_').pop();
		if(extension == 'xml'){
			file = Globals.parseXmlVariables(Xml.parse(file).firstElement(), scope);
		}

		return file;

	}

	public static function getFileByURL(url:String):Dynamic
	{
		var fileName:String = _filesByURL.get(url);
		return getFile(fileName);
	}

	public static function getURLByName(fileName:String):String
	{
		var loadObject:LoadObject = _files.get(fileName);
		if(loadObject == null) return null;
		return loadObject.url;
	}

	public static function getBitmap(fileName:String):Dynamic
	{
		var url:String = getURLByName(fileName);
		if(url == null){
			return null;
		}

		if(!online){
			var b:Bitmap = new Bitmap(getFileByURL(url).bitmapData);
			b.smoothing = true;
			return b;
		}

		if(cachedBitmapData.get(url) != null){
			var b:Bitmap = new Bitmap(cachedBitmapData.get(url));
			b.smoothing = true;
			return b;
		} else {
			var bmd:BitmapData = getFileByURL(url).bitmapData;
            cachedBitmapData.set(url, bmd);
			var b:Bitmap = new Bitmap(bmd);
			b.smoothing = true;
			return b;
		}
	}

	public static function getBitmapData(fileName:String):BitmapData
	{
		if(getFile(fileName) == null){
			return null;
		}

		// if(!online){
		// 	return getFile(fileName).bitmapData;
		// }

		if(cachedBitmapData.get(fileName) == null){
			var bmd:BitmapData = getFile(fileName).bitmapData;
            cachedBitmapData.set(fileName, bmd);
		}

		return cachedBitmapData.get(fileName);

	}

	public static function loadFile(name:String, url:String, queue:String=null, channel:String=null, onLoadCallback:LoadObject->Void=null, callbackScope:Dynamic=null, childQueue:String=null):LoadObject
	{
		var fileName:String = _filesByURL.get(url);
		var loadObject:LoadObject = _files.get(fileName);

		if(loadObject == null){

			var extension:String = url.split('.').pop();
			var urlRequest:URLRequest = new URLRequest(url);
			var loader:Dynamic = null;
			var type:String;

			if(!online){
				var rx:EReg = ~/[\.\-\/]/g;
				url = rx.replace(url.toLowerCase(),'_');
				if(url.indexOf('/') == -1){
					url = basePath+'assets_'+url;
				}
			} else {
				if(Utils.valueInArray(extension, _extensions_sound)){
					loader = new Sound();
					#if flash
						loader.load(urlRequest);
					#else
						loader.load(urlRequest, null, true);
					#end

				} else if(Utils.valueInArray(extension, _extensions_image)){
					loader = new Loader();
					loader.load(urlRequest);
					loader = loader.contentLoaderInfo;
				} else {
					loader = new URLLoader(urlRequest);
				}
			}

			loadObject = new LoadObject(name, url, loader, queue, onLoadCallback, callbackScope, childQueue);
			loadObject.channel = channel;
			
			_filesByURL.set(url,name);
			_files.set(name, loadObject);
			#if cpp
				if(sys.FileSystem.exists(url)){
					if(Utils.valueInArray(extension, _extensions_sound)){
						urlRequest = new URLRequest(url);
						var s:Sound = new Sound();
						s.load(urlRequest, null, true);
						loadObject.file = s;
					} else if(Utils.valueInArray(extension, _extensions_image)){
						loadObject.file = new Bitmap(BitmapData.load(url));
					} else {
						loadObject.file = sys.io.File.getContent(url);
					}
				} else {
					cpp.Lib.println('MISSING : '+url);
				}
			#end
		} else {
			_files.set(name, loadObject);
			onFileComplete(loadObject);
		}
		return loadObject;
	}


	public static function addFile(name:String, url:String, queue:String=null, channel:String=null, onLoadCallback:LoadObject->Void=null, callbackScope:Dynamic=null, childQueue:String=null):LoadObject
	{
		if(queue != null){
			getQueue(queue).push([name, url, queue, channel, onLoadCallback, callbackScope, childQueue]);
			return null;
		}
		return loadFile(name, url, queue, channel, onLoadCallback, callbackScope, childQueue);
	}

	public static function addEventListener(type : String, listener : Dynamic -> Void, ?useCapture : Bool, ?priority : Int, ?useWeakReference : Bool) : Void
	{
		var thisListener:Array<Dynamic> = [type,listener];
		listeners.push(thisListener);
		dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
	}

	public static function removeEventListener(type : String, listener : Dynamic -> Void) : Void
	{
		dispatcher.removeEventListener(type, listener);
	}

	public static function onFileProgress(loader:Dynamic=null):Void
	{
		if(progressHandler != null && getQueue(currentQueue) != null){
			var queueLength:Int = getQueue(currentQueue).length;
			var fileProgress:Float = 0;
			if(loader != null) if(Reflect.getProperty(loader,'bytesTotal') != 0) fileProgress = Reflect.getProperty(loader,'bytesLoaded')/Reflect.getProperty(loader,'bytesTotal')/queueLength;
			var progress:Float = queueIndex/queueLength+fileProgress;
			Reflect.callMethod(scope, progressHandler, [progress]);
		}
	}

	public static function onFileComplete(loadObject:LoadObject):Void
	{
		if(loadObject.file == null) return;
		if(completeHandler != null){
			Reflect.callMethod(scope, completeHandler, [loadObject]);
		}
		if(loadObject.queue != null){
			var extension:String = (online) ? loadObject.url.split('.').pop() : loadObject.url.split('_').pop();
			if(extension == 'xml'){

				loadFilesFromXml(getFile(loadObject.name), loadObject.childQueue);

			} else if(Utils.valueInArray(extension, _extensions_image)){
				var bmd:BitmapData = loadObject.file.bitmapData;
	            cachedBitmapData.set(loadObject.name, bmd);

			} else if(Utils.valueInArray(extension, _extensions_font)){

			}
			onFileProgress();
			loadFromQueue();
		}

	}

	public static function clear():Void
	{
		for(loadObject in _files)
		{
			loadObject.destroy();
		}
		for(bm in cachedBitmapData)
		{
			bm.dispose();
		}
		_files = new Map<String, LoadObject>();
		cachedBitmapData = new Map<String, BitmapData>();
	}

	public static function onError(event:ErrorEvent):Void
	{
		trace(event.text);
	}
	
	public static function onIOError(event:IOErrorEvent):Void
	{
		trace(event.text);
	}
}